#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G09 -- S03 ------------------------------------------------------------##
__all__ = []

from .. import comparecards as cmp_cards
from .. import formlib as layout
from .. import helpstrings as hs
from .. import pilelib as stack
from ..gamelib import info as gi, game
from ..packlib import info as cs

GT, SL = gi.GT, gi.SL
StackWrapper = stack.StackWrapper
FullStackWrapper = stack.FullStackWrapper


class Foundation(stack.AbstractFoundation):
    """
    Build same seq in rank by same suit from choice.
    """

    def isSequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in match order of other _foundation_.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        ncards = len(self.cards)
        for crds in (pile.cards for pile in self.game.s['foundations']):
            if len(crds) > ncards:
                return cmp_cards.isSameRank(cards, rank=crds[ncards]['rank'])
        else:
            return True

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_REGARDLESS


class AnySS_Foundation(stack.AbstractFoundation):
    """
    Build same ranks as W neighbor by same suit from base. W sets choice.
    """

    @property
    def _pile(self):
        """
        cardsBlock() -> bool

        Left pile cards or [] when leftmost.

        :return: pile is blocked?
        :rtype: boolean
        """
        i = list(self.game.s['foundations']).index(self) - 1
        return self.game.s['foundations'][i].cards if i > -1 else []

    def cardsBlock(self):
        """
        cardsBlock() -> bool

        Check if pile is blocked by seeing if _pile has gap.

        :return: pile is blocked?
        :rtype: boolean
        """
        return bool(self._pile)

    def isSequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in same suit and rank in _pile, except leftmost.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        game, pile, card = self.game, self._pile, cards[-1]
        if not pile and self.cap['base_rank'] == cs.ANY:
            game.saveAllCap(game.s['foundations'], base_rank=card['rank'])
            return True
        if self.cap['suit'] == cs.ANY:
            self.game._save_stack_caps(self, suit=card['suit'])
        return (not pile or any(card['rank'] == c['rank'] for c in pile))

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.BUILD_REGARDLESS_SS


class RankSS_Foundation(stack.RK_Foundation):

    def isSequence(self, cards):
        """
        _sequence(cards:obj) -> bool

        Are cards are in match order of other _foundation_.

        :param sequence cards: cards from sending pile
        :return: right sequence?
        :rtype: boolean
        """
        if not super(RankSS_Foundation, self)._sequence(cards):
            return False
        ncards = len(self.cards)
        for crds in (pile.cards for pile in self.game.s['foundations']):
            if len(crds) > ncards:
                return cmp_cards.isSameSuit(cards, suit=crds[ncards]['suit'])
        else:
            return True


class Osmosis(game.Game):
    """
    Finish cards on _foundation_ without limit turn.

    _row_
      Pile 4 vert E. Init deal 4 card. Build none. Play top.

    _talon_
      Pile 1 S. Deal 1 card _waste_.

    _waste_
      Pile 1 S _talon_. Build none. Play top.

    _foundation_
      Pile 4 vert E _row_. Init deal 1 base card. Build same seq in rank by
      same suit from choice. Play none.
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=INFINITE)
    :cvar Stack _foundation_: Foundation (base_rank=ANY, max_move=0)
    :cvar Stack _row_: stack.BasicRowStack
    """
    __info__ = dict(id=59, name='Osmosis', game_type=(GT.ONE_DECK | GT.GNOME),
                    decks=1, redeals=cs.INFINITE, skill_level=SL.MOSTLY_LUCK,
                    version='3.00')
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = StackWrapper(stack.WasteTalon, max_rounds=cs.INFINITE)
    _waste_ = stack.WasteStack
    _foundation_ = StackWrapper(Foundation, base_rank=cs.ANY, max_move=0)
    _row_ = stack.BasicRowStack

    def _create_game(self, playcards=4, **kw):
        l, s = layout.Layout(self), self.s
        dxr = l.XM + l._get_stack_width(playcards, l.XS)
        self.table_size = (2 * (l.XM + l.XS) + dxr + l._get_stackwidth(l.NSUITS),
                     l.YM + 4 * l.YS)
        x, y, = l.XM, l.YM
        for i in range(4):
            s['rows'].append(self._row_(x, y, game=self, offset=(l.XOFFSET, 0)))
            y += l.YS
        x += dxr
        y = l.YM
        for suit in l.SUITS:
            s['foundations'].append(self._foundation_(x, y, game=self, suit=suit,
                                                   offset=(l.XOFFSET, 0)))
            y = l.YS
        x, y, = self.width - l.XS, l.YM + l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'sw')
        if self._waste_ is not None:
            s['wastes'].append(self._waste_(x, y + l.YS, game=self))
            l.set_ncards_text(s['wastes'][-1], 'sw')
        l.defaultStackGroups()
        return l

    def _start(self, rows=4, flip=False, **kw):
        base_rank = self.s['talon'].TOPCARD['rank']
        self.s['talon'].dealBaseCards(ncards=1, saveinfo=False)
        self.game.saveAllCap(stack, base_rank=base_rank)
        self._play_sound('deal')
        for i in range(rows - 1):
            self.s['talon'].dealRow(flip=flip)
        self.s['talon'].dealRow()
        self.s['talon'].deal_cards()


class Peek(Osmosis):
    """
    Finish cards on _foundation_ without limit turn.

    _row_
      Pile 4 vert E. Init deal 4 open card. Build none. Play top.

    _talon_
      Pile 1 S. Deal 1 card _waste_.

    _waste_
      Pile 1 S _talon_. Build none. Play top.

    _foundation_
      Pile 4 vert E _row_. Init deal 1 base card. Build same seq in rank by
      same suit from choice. Play none.
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=INFINITE)
    :cvar Stack _foundation_: Foundation (base_rank=ANY, max_move=0)
    :cvar Stack _row_: stack.BasicRowStack
    """
    __info__ = dict(id=60, name='Peek', game_type=GT.ONE_DECK, decks=1,
                    redeals=cs.INFINITE, skill_level=SL.MOSTLY_LUCK,
                    version='0.80fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _start(self, **kw):
        kw.setdefault('flip', True)
        super(Peek, self)._start(**kw)


class TreasureTrove(Osmosis):
    """
    Finish cards on _foundation_ without limit turn.

    _row_
      Pile 4 vert E. Init deal 4 card. Build none. Play top.

    _talon_
      Pile 1 S. Deal 3 card _waste_.

    _waste_
      Pile 1 S _talon_. Build none. Play top.

    _foundation_
      Pile 4 vert E _row_. Init deal 1 base card W. Build same ranks as W
      neighbor by same suit from base. W sets choice. Play none.

    seealso::
      <http://en.wikipedia.org/wiki/Osmosis_%28solitaire%29>
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=INFINITE,
                                                num_deal=3)
    :cvar Stack _foundation_: AnySS_Foundation (base_rank=ANY, max_move=0,
      suit=ANY)
    :cvar Stack _row_: stack.BasicRowStack
    """
    __info__ = dict(id=715, name='Treasure Trove', game_type=GT.ONE_DECK,
                     decks=1, redeals=cs.INFINITE, skill_level=SL.MOSTLY_LUCK,
                     version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = StackWrapper(stack.WasteTalon, max_rounds=cs.INFINITE,
                           num_deal=3)
    _foundation_ = FullStackWrapper(AnySS_Foundation, base_rank=cs.ANY,
                                    suit=cs.ANY, max_move=0)

    def _start(self, rows=4, flip=False):
        self.s['talon'].dealRow(rows=self.s['foundations'][:1])
        self._play_sound('deal')
        for i in range(rows - 1):
            self.s['talon'].dealRow(flip=flip)
        self.s['talon'].dealRow()
        self.s['talon'].deal_cards()


class PeekII(TreasureTrove):
    """
    Finish cards on _foundation_ without limit turn.

    _row_
      Pile 4 vert E. Init deal 4 card. Build none. Play top.

    _talon_
      Pile 1 S. Deal 3 card _waste_.

    _waste_
      Pile 1 S _talon_. Build none. Play top.

    _foundation_
      Pile 4 vert E _row_. Init deal 1 base card W. Build same ranks as W
      neighbor by same suit from base. W sets choice. Play none.

    seealso::
      <http://en.wikipedia.org/wiki/Osmosis_%28solitaire%29>
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=INFINITE,
                                                num_deal=3)
    :cvar Stack _foundation_: AnySS_Foundation (base_rank=ANY, max_move=0,
      suit=ANY)
    :cvar Stack _row_: stack.BasicRowStack
    """
    __info__ = dict(id=716, name='Peek Ⅱ', game_type=GT.ONE_DECK, decks=1,
                    redeals=cs.INFINITE, skill_level=SL.MOSTLY_LUCK,
                    version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _start(self, **kw):
        kw.setdefault('flip', True)
        super(PeekII, self)._start(**kw)


class OpenPeek(Peek):
    """
    Finish cards on _foundation_ with a turn.

    _row_
      Pile 4 vert E. Init deal 5 open card. Build none. Play top.

    _reserve_
      Pile 32 over 4 row S. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 4 vert E _row_. Build same seq in rank by same suit from choice.
      Play none.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: Foundation (base_rank=ANY, max_move=0)
    :cvar Stack _row_: stack.BasicRowStack
    :cvar Stack _reserve_: stack.OpenStack
    """
    __info__ = dict(id=298, name='Open Peek', game_type=(GT.ONE_DECK |
                    GT.OPEN | GT.ORIGINAL), decks=1, redeals=0,
                    skill_level=SL.MOSTLY_SKILL, version='0.80fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _reserve_ = stack.OpenStack

    def _create_game(self, reserves=(8,) * 4, **kw):
        l, s = super(OpenPeek, self)._create_game(**kw), self.s
        y = self.height
        self.table_size = (self.width, self.height + len(reserves) * l.YS)
        for reserve in reserves:
            x = l.XM
            for i in range(reserve):
                s['reserves'].append(self._reserve_(x, y, game=self))
                x += l.XS
            y += l.YS
        l.defaultStackGroups()
        return l

    def _start(self, rows=5):
        for i in range(rows):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['reserves'])


class Genesis(game.Game):
    """
    Finish cards on _foundation_ with a turn.

    _row_
      Pile 26 over 2 rows. Init deal 1 card N 3 card S. Build none. Play top.

    _foundation_
      Pile 4 vert E _row_. Build same seq in rank by same suit from choice.
      Play none.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: Foundation (base_rank=ANY, max_move=0)
    :cvar Stack _row_: stack.BasicRowStack
    """
    __info__ = dict(id=370, name='Genesis', game_type=(GT.ONE_DECK |
                    GT.OPEN | GT.ORIGINAL), decks=1,
                    skill_level=SL.MOSTLY_SKILL, version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation_ = StackWrapper(Foundation, base_rank=cs.ANY, max_move=0)
    _row_ = stack.BasicRowStack

    def _create_game(self, rows=(13,) * 2, reserves=0, **kw):
        l, s = layout.Layout(self), self.s
        dy = l.YS - 3 * l.YOFFSET
        self.table_size = (l.XM + rows * l.XS,
                     l.YM + 2 * dy + l._get_stack_height(l.NSUITS, l.YS),)
        x, y, = l.XM + (rows - l.NSUITS) * l.XS / 2, l.YM
        for suit in l.NSUITS:
            s['foundations'].append(self._foundation_(x, y, game=self,
                                            suit=suit, offset=(0, l.YOFFSET)))
            x += l.XS
        y = self.height - 2 * dy
        for row in rows:
            x = l.XM
            for i in range(row):
                s['rows'].append(self._row_(x, y, game=self))
                x += l.XS
            y += dy
        s['talon'] = self._talon_(l.XM, l.YM, game=self)
        l.defaultStackGroups()
        return l

    def _start(self):
        for i in range(3):
            self.s['talon'].dealRow(rows=self.s['rows'][13:], frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['rows'][:13])


class GenesisPlus(Genesis):
    """
    Finish cards on _foundation_ with a turn.

    _row_
      Pile 26 over 2 rows. Init deal 1 card N 3 card S. Build none. Play top.

    _reserve_
      Pile 2 horz NW. Build 1 card. Play top.

    _foundation_
      Pile 4 vert E _row_. Build same seq in rank by same suit from choice.
      Play none.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: Foundation (base_rank=ANY, max_move=0)
    :cvar Stack _row_: stack.BasicRowStack
    :cvar Stack _reserve_: stack.ReserveStack
    """
    __info__ = dict(id=371, name='Genesis +', game_type=(GT.ONE_DECK |
                    GT.OPEN | GT.ORIGINAL), decks=1, redeals=0,
                    skill_level=SL.MOSTLY_SKILL, version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _reserve_ = stack.ReserveStack

    def _create_game(self, reserves=2, **kw):
        l, s = super(GenesisPlus, self)._create_game(**kw), self.s
        x, y = l.XM, l.YM
        for i in range(reserves):
            s['reserves'].append(self._reserve_(x, y, game=self))
            x += l.XS


class Bridesmaids(game.Game):
    """
    Finish cards on _foundation_ without limit turn.

    _talon_
      Pile 1 NW. Deal 3 card _waste_.

    _waste_
      Pile 1 S _talon_. Build none. Play top.

    _foundation_
      Pile 4 vert E. Init deal 1 base card W. Build same ranks as W neighbor
      by same suit from base. W sets choice. Play none.
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=INFINITE,
      num_deal=3)
    :cvar Stack _waste_: stack.WasteStack
    :cvar Stack _foundation_: AnySS_Foundation (base_rank=ANY, suit=ANY,
      max_move=0)
    """
    __info__ = dict(id=409, name='Bridesmaids', game_type=GT.ONE_DECK,
                    decks=1, redeals=cs.INFINITE, skill_level=SL.MOSTLY_LUCK,
                    version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = StackWrapper(stack.WasteTalon, max_rounds=cs.INFINITE, num_deal=3)
    _waste_ = stack.WasteStack
    _foundation_ = StackWrapper(AnySS_Foundation, base_rank=cs.ANY, suit=cs.ANY,
                                max_move=0)

    def _create_game(self, playcards=12, **kw):
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 3 * l.XS + l._get_stack_width(playcards), l.YM + 4 * l.YS)
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'se')
        s['wastes'].append(self._waste_(x, y + l.YS, game=self))
        l.set_ncards_text(s['wastes'][-1], 'se')
        x, y = l.XM + 2 * l.XS, l.YM
        for i in l.NSUTIS:
            s['foundations'].append(self._foundation_(x, y, game=self,
                                                   offset=(l.XOFFSET, 0)))
            y += l.YS
        l.defaultStackGroups()
        return l

    def _start(self, flip=0):
        self.s['talon'].dealRow(rows=self.s['foundations'][:1])
        self._play_sound('deal')
        self.s['talon'].deal_cards()


class Choice(game.Game):
    """
    Finish cards on _foundation_ within 1 turn.

    _row_
      Pile 8 horz S _foundation_. Build down in rank by alt color. Play seq.

    _foundation_
      Pile 8 horz N. Build up in rank in same suit order. Play top.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: RankSS_Foundation (dir=0, max_cards=52)
    :cvar Stack _waste_: stack.WasteStack
    """
    __info__ = dict(id=755, name='Choice', game_type=(GT.THREE_DECK | GT.OPEN
                    | GT.ORIGINAL), decks=3, version='1.10fc',
                    skill_level=SL.MOSTLY_SKILL, ranks=cs.SHORT_A6K[1:])
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation_ = StackWrapper(RankSS_Foundation, dir=0, max_cards=52)
    _row_ = stack.AC_RowStack

    def _create_game(self, rows=8, playcards=16):
        l, s = layout.Layout(self), self.s
        max_rows = max(l.NRANK, rows)
        dy = [l._get_stack_height(l.NCARDS), l._get_stack_height(playcards)]
        self.table_size = (l.XM + max_rows * l.XS, l.YM + sum(dy))
        center = lambda p: l.XM + (max_rows - p) * l.XS / 2
        y = l.YM
        for x, rank in zip(l._row_iter(center(l.NRANK), l.NRANK), l.RANK):
            s['foundations'].append(self._foundation_(x, y, game=self,
                                       base_rank=rank, offset=(0, l.YOFFSET)))
        y += dy[0]
        for x, rank in l._row_iter(center(rows), rows):
            s['rows'].append(self._row_(x, y, game=self))
        x, y = self.width - l.XS, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.defaultStackGroups()
        return l

    def _start(self, rows=12):
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRowAvail()


PROVIDES = [Osmosis, TreasureTrove, Peek, OpenPeek, Genesis, GenesisPlus,
            Bridesmaids, PeekII, Choice]